/* License Notice:
**
** This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
** This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
**   GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
**   along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file character.cpp
 * @author TooOld2Rock'nRoll
 * @see character.hpp
 */
/*---- Includes ----*/
#include "character.hpp"

#include "toolbox/debug.h"


/*---- Global Variables ----*/


/*---- Methods Implementation ----*/
/**
 * @brief Character Base Constructor.
 *
 * @param domain_id - the domain identification to use when loading resources.
 * @param sh - reference to Shader to be used on all render operations.
 * @param sprite_count - how many sprites should be pre-allocated.
 * @param audio_effect_count - how many audio effects should be pre-allocated.
 */
Character::Character (DomainID_t domain_id, Shader *sh, unsigned sprite_count, unsigned audio_effect_count) :
                GameObject(domain_id, sh, sprite_count), ui_audio_count(audio_effect_count)
{
    int i =0;
    Sprite2D *slist = nullptr;

    _debug (TRACE, "::%s ()", __FUNCTION__);

    //change to this derived class kind
    this->obj_kind = GameObject::CHARACTER;

    this->p_ev_mng_instance = &EventManager::instance ();

    //make sprite list work as a batch
    this->v3_world_position.entangle (this->v3sp_position_anchor);
    slist = this->getSprites ();
    for (i = this->getSpriteCount(); i--; )
        slist[i].getPosition().entangle (this->v3sp_position_anchor);

    this->v_states_list.resize (character_state_t::_state_max);

    this->v_command_list.resize (character_commands_t::_command_max);

    //tree root will always be an invalid state change
    this->nt_chain_actions.insert (new Character::chain_action_s ());

    if (audio_effect_count) //> 0
        this->ap_audio_effects = new Audio::Effect [this->ui_audio_count];
}//End Constructor


/**
 * @brief Destructor.
 */
Character::~Character ()
{
    int i;
    Sprite2D *slist = nullptr;

    //probably not necessary since the sprites are going to be deleted in the superclass.
    slist = this->getSprites ();
    for (i = this->getSpriteCount(); i--; )
        slist[i].getPosition().unentangle ();

    if (this->ap_audio_effects)
        delete []this->ap_audio_effects;
}//end Destructor


/**
 * @brief Changes the Character current state.
 *
 * State change will be blocked if not a valid state transition.<br>
 * The list of valid states for this base class is the enum character_state_t, but considering the user may want to
 *    extend this list and override the method, unsigned is being used as parameter since C style enums are just that.<br>
 * Actual state change implementation and status handling for each state are implemented by its own classes.
 *
 * @param new_state - the new state to go to.
 *
 * @return <b>true</b> if new state is valid, <b>false</b> if state change not possible.
 *
 * @throws std::out_of_range - if the new state is greater than the max number of states supported.
 * @throws std::runtime_error - if new state is null (was not initiated).
 */
bool Character::setState (unsigned new_state)
{
    bool st_valid = false;

    _debug (TRACE, "::%s (new_state=%u)", __FUNCTION__, new_state);

    /*
     * std::vector should take care of this!?
     *if (new_state >= this->v_states_list.capacity ())
     *    throw std::out_of_range (fmt::format ("{0}:{1} - New state out of range, {2} of {3}!", __LINE__, __FILE__, new_state, character_state_t::_state_max - 1));
     */
    if (!this->v_states_list[new_state])
        throw std::runtime_error (fmt::format ("{0}:{1} - New state {2} is null, can't change to a null state!", __LINE__, __FILE__, new_state));

    if (new_state == this->u_current_state)
        return true; //just ignore it...

    if (!this->v_states_list[this->u_current_state])
        st_valid = true; //we can't validade a null state, but the next one is valid, so it's ok?
    else if (new_state == character_state_t::LOADING) //&& this->_current_state == character_state_t::_state_max
        st_valid = true; //_state_max is only valid right after the constructor exits, we must transit through Loading first!
                         //and Loading needs no validation since it has its own dedicated method.
    else if (this->u_current_state < this->v_states_list.capacity ())
        st_valid = static_cast<State<Character> *>(this->v_states_list[this->u_current_state])->checkStateChange (*this, new_state);

    if (st_valid)
    {
        this->u_current_state = new_state; //we can only get here if all the consistency checks were ok!
        static_cast<State<Character> *>(this->v_states_list[this->u_current_state])->enter (*this);
    }//end if (st_valid)
#ifdef ENABLE_DEBUG
    else
        _debug (WARNING, "WARNING: invalid state from %u to %u!", this->u_current_state, new_state);
#endif

    return st_valid;
}//End setState ()


/**
 * @brief Helper method to set the animation FPS of all the Character sprites.
 *
 * @param fps - FPS to use on animations.
 * @param loop - <b>true</b> if all animations should loop (probably not!).
 */
void Character::setAnimationFPS (float fps, bool loop)
{
    int i =0;
    Sprite2D *sp = this->getSprites ();

    _debug (TRACE, "::%s (fps=%.2f)", __FUNCTION__, fps);

    for (i = this->getSpriteCount (); i--; )
        sp[i].setAnimationFPS (fps, loop);
}//End setAnimationFPS ()


/**
 * @brief Changes the animation (Sprite with tiles mapped from the Texture) used during update.
 *
 * @param index - the Sprite index to use.
 *
 * @throws std::out_of_range - if the index is greater than the number of Sprites in the object.
 */
void Character::setActiveSprite (unsigned index)
{
    _debug (TRACE, "::%s (index=%u)", __FUNCTION__, index);

    if (index >= this->getSpriteCount ())
        throw std::out_of_range (fmt::format ("{0}:{1} - Animation out of range, {2} of max {3}!",
                                                                __LINE__, __FILE__, index, this->getSpriteCount() - 1));

    if (this->u_active_sprite != index)
        this->getSprite(index).resetAnimation (true);

    this->u_active_sprite = index;
}//End setAnimation ()


/**
 * @brief Tries to play a Character's audio effect in a random free audio slot.
 *
 * @param index - the audio effect index to use.
 *
 * @throws std::out_of_range - if the index is greater than the number of audio effects in the object.
 */
void Character::playEffect (unsigned index)
{
    _debug (TRACE, "::%s (index=%u)", __FUNCTION__, index);

    if (index >= this->ui_audio_count)
        throw std::out_of_range (fmt::format ("{0}:{1} - Animation out of range, {2} of max {3}!",
                                                                __LINE__, __FILE__, index, this->ui_audio_count - 1));
    else if (!this->ap_audio_effects[index].getSource())
    {
        _debug (ERROR, "Character Effect %u is null, can't play it!", index);
        return;
    }//end else if (!this->ap_audio_effects[index])

    this->ap_audio_effects[index].getPosition().set (this->v3_world_position.get ());
    Audio::instance().play (&this->ap_audio_effects[index]);
}//end playEffect ()


/**
 * @brief @brief Gets a reference to a specific SoundEffect.
 *
 * @param index - audio effect index.
 *
 * @return Reference to the desired audio effect.
 *
 * @throws std::runtime_error - if the audio effect list was not initialized yet for some reason...
 * @throws std::out_of_range - if the audio effect index is greater than the max number of audio effect supported.
 */
Audio::Effect& Character::getAudioEffect (unsigned index) const
{
    _debug (TRACE, "::%s (index=%u)", __FUNCTION__, index);

    if (!this->ap_audio_effects)
        throw std::runtime_error (fmt::format ("{0}:{1} - Character has no audio effect list allocated!", __LINE__, __FILE__));
    else if (index >= this->ui_audio_count)
        throw std::out_of_range (fmt::format ("{0}:{1} - Audio effect out of range, {2} of {3}!", __LINE__, __FILE__, index, this->ui_audio_count - 1));

    return this->ap_audio_effects[index];
}//End getSprite ()



/**
 * @brief Helper method to set the same scale factor to all the Character sprites.
 * @see Sprite2D::setScaleFactor
 *
 * @param s_factor - how much to scale.
 */
void Character::setScaleFactor (float s_factor)
{
    int i = 0;
    Sprite2D *sp = this->getSprites ();

    _debug (TRACE, "::%s (s_factor=%.2f)", __FUNCTION__, s_factor);

    for (i = this->getSpriteCount (); i--; )
        sp[i].getSize().setScaleFactor (s_factor);
}//End setScaleFactor ()


bool Character::_findInputInChain_cb (Character::chain_action_s &link, controller_button_map_t *key)
{
    _debug (VERBOSE, "::%s (%s == %u)?", __FUNCTION__, sa_input_map_names[link._button], *key);

    return (link._button == *key);
}//end _findInputInChain_cb ()

/**
 * @brief Adds a input chain to the chain action tree for handling special moves.
 *
 * @param chain - the sequence of input keys to follow.
 * @param key_sts - the state of each key to follow.
 * @param state_to_go - the state to go to when the chain ends successfully.
 * @param command_to_exe - the command to execute when the chains ends successfully.
 *
 * @remarks HOLD state will be ignored unless explicitly required, since there is a timeout between chain links, this
 *    helps avoiding misfired checks if the player holds or not a key for too long triggering a HOLD event that is irrelevant.
 */
void Character::mapChainAction (std::vector<controller_button_map_t> &chain, std::vector<key_state> &key_sts,
                                unsigned state_to_go, unsigned command_to_exe)
{
    unsigned i = 0,
             chain_len = chain.size ();
    Character::chain_action_s *aux = nullptr;

    _debug (TRACE, "::%s (state=%u, command=%u)", __FUNCTION__, state_to_go, command_to_exe);

    if (chain_len == 0)
        throw std::invalid_argument (fmt::format ("{0}:{1} - can't map a chain with 0 inputs!", __LINE__, __FILE__));
    else if (chain_len != key_sts.size ())
        throw std::invalid_argument (fmt::format ("{0}:{1} - the length of the key chain and states are different!", __LINE__, __FILE__));
    else if (chain_len == 1)
        _debug (WARNING, "It is strange to map a action chain with just one input, are you sure?");

    this->nt_chain_actions.root ();
    for (i=0; i < chain_len - 1; ++i)
    {
        if (key_sts[i] == key_state::HOLD)
        {
            _debug (WARNING, "HOLD events are ignored in action chains, please revise the map!");
            continue;
        }//end if (key_sts == key_state::HOLD)

        aux = nullptr;
        aux = this->nt_chain_actions.searchInBranch (&chain[i],
                                (NTree<Character::chain_action_s>::SearchNodeCallback)Character::_findInputInChain_cb);

        if (!aux)
        {
            aux = new Character::chain_action_s ();
            aux->_button = chain[i];

            this->nt_chain_actions.insert (aux);
            this->nt_chain_actions.reset (); //point to the first leaf, otherwise would be at the end of list...
        }//end if (!this->nt_chain_actions.searchInBranch (chain[i]))

        aux->b_state[key_sts[i]] = true;

        //if we are inserting a new link or just following previous chains, we should get here always at the right leaf!
        this->nt_chain_actions.down ();
    }//end for (input_map_t input : chain)

    //the last link in the chain is different and must be considered final!
    aux = nullptr;
    aux = this->nt_chain_actions.searchInBranch (&chain[i],
                        (LinkedList_Si<Character::chain_action_s>::SearchNodeCallback)Character::_findInputInChain_cb);
    if (aux)
        throw std::runtime_error (fmt::format ("{0}:{1} - Chain already mapped as final, can´t overwrite the previous leaf!", __LINE__, __FILE__));

    aux = new Character::chain_action_s ();
    aux->b_final = true;
    aux->_button = chain[i];
    aux->b_state[key_sts[i]] = true;
    aux->_next_state = state_to_go;
    aux->_command_index = command_to_exe;

    this->nt_chain_actions.insert (aux);
    this->nt_chain_actions.root ();
}//End mapChainAction ()


/**
 * @brief Set character orientation to face the current target.
 *
 * @return The new orientation.
 */
Character::facing_e Character::faceTarget ()
{
    //check for target position update
    if (this->vec3p_target)
    {
        if (this->vec3p_target->x < this->v3sp_position_anchor->x)
            this->_facing_to = Character::FACE_LEFT;
        else// if (this->vec3p_target.x > this->v3_world_position.x)
            this->_facing_to = Character::FACE_RIGHT;
    }//end if (this->vec3p_target)

    return this->_facing_to;
}//End faceTarget ()


/**
 * @brief Interface to Input Handle Callback.
 *
 * @param input - which input state has changed.
 * @param input_st - the input new state.
 * @param delta_t - time elapsed since last game loop iteration.
 *
 * @throws std::out_of_range - if the input is greater than the max number of inputs supported.
 * @throws std::runtime_error - if current state is null (was not initiated).
 */
void Character::handleInput (controller_button_map_t input, key_state input_st, double delta_t)
{
    static Character::chain_action_s *aux = nullptr;

    _debug (TRACE, "::%s (input=%u, state=%u)", __FUNCTION__, input, input_st);

    if (input >= controller_button_map_t::_button_max)
        throw std::out_of_range (fmt::format ("{0}:{1} - Input out of range, {2} of {3}!", __LINE__, __FILE__, input, controller_button_map_t::_button_max));
    else if (!this->v_states_list[this->u_current_state])
        throw std::runtime_error (fmt::format ("{0}:{1} - State {2} is null!", __LINE__, __FILE__, this->u_current_state));

    if (input_st != key_state::HOLD) //ignoring HOLD
    {
        //check for a chain action...
        aux = this->nt_chain_actions.searchInBranch (&input,
                (NTree<Character::chain_action_s>::SearchNodeCallback)Character::_findInputInChain_cb);

        if (!aux) //failed to find the next link, reset chain!
            this->nt_chain_actions.root ();
        else if (!aux->b_state[input_st]) //not the correct state, reset chain!
            this->nt_chain_actions.root ();
        else //possible candidate
        {
            this->f_chain_action_time_out = this->_statistics.f_chain_action_time_out;
            aux = this->nt_chain_actions.down ();

            if (aux->b_final)
            {
                if (this->setState (aux->_next_state))
                    this->execute (aux->_command_index, delta_t);

                this->nt_chain_actions.root ();

                return; //since we just changed states, we can't redirect the event to the new state.
            }//end if (aux->b_final)
        }//end else
    }//end if (input_st != key_state::HOLD)


    //redirect input to the current state handler...
    static_cast<State<Character> *>(this->v_states_list[this->u_current_state])->handleInput (*this, input, input_st, delta_t);
}//end handleInput ()


/**
 * @brief Execute a specific action from the actions list.
 *
 * @param action - action to execute.
 * @param delta_t - time elapsed since last game loop iteration.
 *
 * @throws std::out_of_range - if the action is greater than the max number of actions supported.
 */
void Character::execute (unsigned action, double delta_t)
{
    _debug (TRACE, "::%s (action=%u)", __FUNCTION__, action);

    if (action == this->v_command_list.size ())
        return; //purposeful invalid state, just ignore...
    else if (action > this->v_command_list.size ())
        throw std::out_of_range (fmt::format ("{0}:{1} - Action out of range, {2} of {3}!", __LINE__, __FILE__, action, character_commands_t::_command_max - 1));
    else if (!this->v_command_list[action])
        throw std::runtime_error (fmt::format ("{0}:{1} - Command {2} is null!", __LINE__, __FILE__, action));

    static_cast<Command<Character> *>(this->v_command_list[action])->execute (*this, delta_t);
}//End execute ()


/**
 * @brief Update the Character based on game change in state since last game loop iteration.
 *
 * @param delta_t - time elapsed since last game loop iteration.
 *
 * @throws std::out_of_range - if the current character state is bigger than the supported states list.
 * @throws std::runtime_error - if current state is null (was not initiated).
 */
void Character::update (double delta_t)
{
    _debug (VERBOSE, "::%s ()", __FUNCTION__);

    /*
     * std::vector should take care of this!?
     * //this shouldn't be possible, but.....
     *if (new_state >= this->v_states_list.capacity ())
     *    throw std::out_of_range (fmt::format ("{0}:{1} - New state out of range, {2} of {3}!", __LINE__, __FILE__, new_state, character_state_t::_state_max - 1));
     */
    if (!this->v_states_list[this->u_current_state])
        throw std::runtime_error (fmt::format ("{0}:{1} - State {2} is null!", __LINE__, __FILE__, this->u_current_state));

    //regulates chain actions over time
    this->f_chain_action_time_out -= delta_t;
    if (this->f_chain_action_time_out <= 0.f)
        this->nt_chain_actions.root (); //timeout reached, reset!

    static_cast<State<Character> *>(this->v_states_list[this->u_current_state])->update (*this, delta_t);
}//End update

/**
 * @brief Draw the Character based on its current state.
 *
 * @param delta_t - time elapsed since last game loop iteration.
 */
void Character::draw ()
{
    Sprite2D *sp = &this->getActiveSprite ();

    _debug (VERBOSE, "::%s ()", __FUNCTION__);

    sp->mirror ((this->_facing_to == Character::FACE_LEFT ? true : false), false);
    sp->draw (*this->obj_shader);
}//End draw ()

